package cn.fanyc.action.blockchain.fbc.contract;

import cn.fanyc.action.blockchain.fbc.contract.api.State;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.shim.ChaincodeStub;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.List;
import java.util.logging.Logger;

/**
 * 商业票据智能合约
 *
 * @author Fanyc
 * @date 2020-10-18 14:45
 */
@Contract(name = "org.papernet.commercialpaper",
        info = @Info(title = "MyAsset Contract", description = "我的商业票据", version = "0.0.1",
                license = @License(name = "License-Identifier: Apache-2.0", url = ""),
                contact = @Contact(email = "yucai_fan@163.com", name = "java-contract", url = "http://www.fanyc.cn")))
@Default
public class CommercialPaperContract implements ContractInterface {

    /**
     * 日志记录器
     */
    private final static Logger LOGGER = Logger.getLogger(CommercialPaperContract.class.getName());

    /**
     * 创建合约上下文场景
     * Create context from {@link ChaincodeStub}.
     * <p>
     * Default impl provided, but can be
     * overwritten by contract
     *
     * @param stub Instance of the ChaincodeStub to use for this transaction
     * @return instance of the context to use for the current transaction being
     * executed
     */
    @Override
    public Context createContext(ChaincodeStub stub) {
        return new CommercialPaperContext(stub);
    }

    /**
     * 执行不存在的合约时回调
     * Invoked for any transaction that does not exist.
     * <p>
     * This will throw an exception. If you wish to alter the exception thrown or if
     * you wish to consider requests for transactions that don't exist as not an
     * error, subclass this method.
     *
     * @param ctx the context as created by {@link #createContext(ChaincodeStub)}.
     */
    @Override
    public void unknownTransaction(Context ctx) {

    }

    /**
     * 每次合约调用前执行
     * Invoked once before each transaction.
     * <p>
     * Any exceptions thrown will fail the transaction, and neither the required
     * transaction or the {@link #afterTransaction(Context, Object)} will be called
     *
     * @param ctx the context as created by {@link #createContext(ChaincodeStub)}.
     */
    @Override
    public void beforeTransaction(Context ctx) {

    }

    /**
     * 每次合约调用后执行
     * Invoked once after each transaction.
     * <p>
     * Any exceptions thrown will fail the transaction.
     *
     * @param ctx    the context as created by
     *               {@link #createContext(ChaincodeStub)}.
     * @param result The object returned from the transaction function if any. As
     *               this is a Java object and therefore pass-by-reference it is
     */
    @Override
    public void afterTransaction(Context ctx, Object result) {

    }

    /**
     * 初始化调用方法
     *
     * @param ctx 上下文
     */
    @Transaction
    public void instantiate(CommercialPaperContext ctx) {
        LOGGER.info("instantiate: No data migration to perform");
    }

    /**
     * 发布票据交易
     *
     * @param ctx              合约上下文
     * @param issuer           发布者
     * @param paperNumber      票据号
     * @param issueDateTime    发布日期
     * @param maturityDateTime 结束日期
     * @param faceValue        票面金额
     * @return 发布的票据对象
     */
    @Transaction
    public Paper issue(CommercialPaperContext ctx, String issuer, String paperNumber, String issueDateTime, String maturityDateTime, int faceValue) {
        LOGGER.info("Call issue transaction: issuer=" + issuer + ",paperNumber=" + paperNumber
                + ",issueDateTime=" + issueDateTime + ",maturityDateTime=" + maturityDateTime + ",faceValue=" + faceValue);

        // 构造票据
        Paper paper = Paper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue, issuer, "");
        // 设置票据为发布状态
        paper.setIssued();
        // 设置票据的拥有者为当前发布者
        paper.setOwner(issuer);
        // 发布票据到合约票据列表管理器中
        ctx.paperList.addPaper(paper);

        return paper;
    }

    /**
     * 获取票据列表
     *
     * @param ctx 合约上下文
     * @return 票据Key列表JSON字符串串
     */
    @Transaction
    public String getKeys(CommercialPaperContext ctx) {
        LOGGER.info("Call getKeys transaction");

        // 获取所有票据Key列表
        List<Object> keys = ctx.paperList.getKeys();
        // 转换为JSON字符串
        JSONArray arr = new JSONArray(keys);

        LOGGER.info("Call getKeys return: "+ arr.toString());
        return arr.toString();
    }

    /**
     * 获取票据详情
     * @param ctx 合约上下文
     * @param key 票据KEY
     * @return 票据对象JSON字符串
     */
    @Transaction
    public String getPaper(CommercialPaperContext ctx, String key) {
        return new JSONObject(ctx.paperList.getPaper(key)).toString();
    }

    /**
     * 购买票据交易
     *
     * @param ctx              合约上下文
     * @param issuer           发布者
     * @param paperNumber      票据号
     * @param currentOwner     当前票据的拥有者（销售者）
     * @param newOwner         新的票据的拥有者（购买者）
     * @param price            买入价格
     * @param purchaseDateTime 购买日期
     * @return 交易的票据对象
     */
    @Transaction
    public Paper buy(CommercialPaperContext ctx, String issuer, String paperNumber, String currentOwner, String newOwner, int price, String purchaseDateTime) {
        // 构造票据Key
        String paperKey = State.makeKey(new String[]{issuer, paperNumber});
        // 从票据列表管理中获取票据对象
        Paper paper = ctx.paperList.getPaper(paperKey);

        // 票据的实际拥有者已不是销售者名下
        if (!currentOwner.equals(paper.getOwner())) {
            throw new RuntimeException(String.format("Paper %s,%s is not owned by %s", issuer, paperNumber, currentOwner));
        }

        // 票据是发行状态，则修改为交易状态
        if (paper.isIssued()) {
            paper.setTrading();
        }
        // 票据是交易状态，则修改拥有者为购买者
        if (paper.isTrading()) {
            paper.setOwner(newOwner);
        } else {
            throw new RuntimeException(String.format("Paper %s,%s is not trading, current statte is %s", issuer, paperNumber, paper.getState()));
        }

        // 通过票据列表管理器更新票据对象
        ctx.paperList.updatePaper(paper);

        return paper;
    }

    /**
     * 回购票据交易
     * @param ctx 合约上下文
     * @param issuer 发布者
     * @param paperNumber 票据号
     * @param redeemingOwner 回购者
     * @param redeemDateTime 回购时间
     * @return 交易的票据对象
     */
    public Paper redeem(CommercialPaperContext ctx, String issuer, String paperNumber, String redeemingOwner, String redeemDateTime) {
        // 构造票据Key
        String paperKey = Paper.makeKey(new String[]{issuer, paperNumber});
        // 从票据列表管理中获取票据对象
        Paper paper = ctx.paperList.getPaper(paperKey);

        // 票据已回购则报异常
        if (paper.isRedeemed()) {
            throw new RuntimeException(String.format("Paper %s,%s already redeemed", issuer, paperNumber));
        }

        // 判断回购者是否为发行者
        if (redeemingOwner.equals(paper.getOwner())) {
            // 设置拥有者为发行者
            paper.setOwner(paper.getIssuer());
            // 设置状态为已回购
            paper.setRedeemed();
        } else {
            throw new RuntimeException(String.format("Redeeming owner does not own paper %s,%s", issuer, paperNumber));
        }

        // 通过票据列表管理器更新票据对象
        ctx.paperList.updatePaper(paper);
        return paper;
    }
}
